// *********************************
// Case Team Helper - Class to manage a Case's 'case team' members. Supports bulk processing
//
// Author: Anil Rhemtulla
//
public class CaseTeamHelper {

	// Constructor
	// Parameters: 
	//		caseIds - The list of case IDs whose case teams we want to modify 
	public void CaseTeamHelper(List<ID> caseIDs){
		this.CaseIDs = caseIDs;
	}
	
	// Custom Exception Class
	public class CaseTeamHelperException extends Exception {}
	
	// Static boolean that returns false if SaveToDatabase has been called before.
	// Note, since this is a static member, this particular instance of CaseTeamHelper
	// might not yet have called SaveToDatabase. Use with great care.
	public static boolean FirstRun = true; 
	
	// Add Case Team Member - Adds a user to the case team. Optionally removes other users from the with role
	// Parameters:
	//		userId - The user ID to add to the case team
	//		caseId - The ID of the case
	//		rolename - The name of the case tean role
	//		removeAllOtherUsersFromRole - Set to true to remove all other users from this role
	// Notes:
	//		the update is not made until SaveToDatabase is called
	public void AddCaseTeamMember(ID userId, ID caseId, string roleName, boolean removeAllOtherUsersFromRole){

		ID roleId = GetRoleID(roleName);
					 
		CaseTeamMember ctm = new CaseTeamMember();
		ctm.MemberId = userId;
		ctm.ParentId = caseId;
		ctm.TeamRoleId = roleId;
		
		QueueForUpsert.add(ctm);
		
		if(removeAllOtherUsersFromRole)
			RemoveAllUsersFromCaseTeamRole(caseId, roleId);
	}
	
	// Remove All Users From Case Team Role - Removes all the users in a particular role from a case's case team.
	// Parameters:
	//		caseId - The id of the case
	//		roleName - The name of the case team role
	public void RemoveAllUsersFromCaseTeamRole(ID caseId, string roleName){
		RemoveAllUsersFromCaseTeamRole(caseId, GetRoleID(roleName));
	}
	
	// Remove All Users From Case Team Role - Removes all the users in a particular role from a case's case team.
	// Parameters:
	//		caseId - The id of the case
	//		roleId - The id of the case team role
	public void RemoveAllUsersFromCaseTeamRole(ID caseId, ID roleID){
		CaseTeamMember ctm = new CaseTeamMember();
		ctm.ParentId = caseId;
		ctm.TeamRoleId = roleId;
		
		QueueForDeletion.add(ctm);
	}

	// Save To Database - Commits all the case team changes to the database
	// Parameters:
	// Notes: If any failure occurs the call to delete or usert will rollback, this could leave
	// things in an inconsistent state. If you're not calling this from a trigger you should consider using
	// a savepoint.
	// Errors will occur if you try to add the same user to multiple different case team roles.
	public void SaveToDatabase()
	{
		if(QueueForDeletion.size() > 0){
			Map<ID, Set<ID>> caseRoleIds = new Map<ID, Set<ID>>();
			Set<ID> roleIds = new Set<ID>();
			for(CaseTeamMember ctm : QueueForDeletion){
				if(caseRoleIds.containsKey(ctm.ParentId)){
					caseRoleIds.get(ctm.ParentId).add(ctm.TeamRoleId);
				}
				else
					caseRoleIds.put(ctm.ParentId, new Set<ID>{ctm.TeamRoleId});
					
				roleIds.add(ctm.TeamRoleId);
			}
			
			List<CaseTeamMember> ctmsToDelete = new List<CaseTeamMember>();
			
			List<CaseTeamMember> possibleRemoveTargets = [Select ID, ParentId, TeamRoleId from CaseTeamMember where ParentId in :caseRoleIds.keySet() and TeamRoleId in :roleIDs];
			for(CaseTeamMember ctm : possibleRemoveTargets){
				Set<ID> rolesToDelete = caseRoleIds.get(ctm.ParentId);
				if(rolesToDelete.contains(ctm.TeamRoleId))
					ctmsToDelete.add(ctm);
			}
			// TODO: We might want to replace the delete and upsert calls with database.delete to allow
			// for partial save on error.
			if(ctmsToDelete.size() > 0)
				delete ctmsToDelete;
		}
		
		if(QueueForUpsert.size() > 0){
			List<Database.UpsertResult> lur = database.upsert(QueueForUpsert, false);
			for(Database.UpsertResult ur : lur){
				if(!ur.isSuccess()){
					List<Database.Error> errors = ur.getErrors();
					if(errors.size() == 1 && errors[0].getStatusCode() == StatusCode.INACTIVE_OWNER_OR_USER){
						// Ignore error - instead of throwing an error when someone tries to add an
						// inactive user to the case team, we are simply ignoring the error.
					}else{
				        // Send an email to notify of failure
				        string message = 'Case Team Helper Errors\r\n\r\n';
				        for(integer i=0 ; i < errors.size(); i++){
				        	message += errors[i].getStatusCode() + ' ' + errors[i].getMessage() + ' ID:' + QueueForUpsert[i].id +  ' Team Role ID:' + QueueForUpsert[i].TeamRoleId + ' Member ID:' + QueueForUpsert[i].MemberId + ' Case ID:' + QueueForUpsert[i].ParentId +'\r\n';
				        }
				        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();           
				        String[] toAddresses = new String[] {'SalesforceDev@attinteractive.com'};            
				        mail.setToAddresses(toAddresses);                    
				        mail.setSubject('Unhandled Apex error in Salesforce Case Team Helper. User:' + UserInfo.getUserName());            
				        mail.setPlainTextBody('The following exception occurrred:\r\n' + message);            
				        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });        						
					}
					
				}	
			}
			
		}			
			
		FirstRun = false;
	}
	
	//PRIVATE METHODS
	
	// Get's the ID of a case role given its name
	private ID GetRoleID(string roleName){
		ID roleID = CaseTeamRoles.get(roleName);
		if(roleID == null)
			throw new CaseTeamHelperException('The Case Team Role:'  + roleName + ' was not found.');
			
		return roleID;		
	}	
	
	// Loads the case teama roles into cache
	private void LoadCaseTeamRoles(){
		List<CaseTeamRole> ctr = [Select Name, ID from CaseTeamRole];
		
		m_caseTeamRoles = new Map<string, ID>();
		
		for(CaseTeamRole role : ctr){
			m_caseTeamRoles.put(role.Name, role.ID);
		}
	}

	private List<ID> CaseIDs;	
	private Map<string, ID> m_caseTeamRoles;
	
	// A map of the case team roles
	private Map<string, ID> CaseTeamRoles{
		get{
			if(null == m_caseTeamRoles)
				LoadCaseTeamRoles();
			return m_caseTeamRoles;
		}		
	}
	
	// The queue of case team members that are queued for deletion
	private List<CaseTeamMember> QueueForDeletion = new List<CaseTeamMember>(); 

	// The queue of case team members that are queued for upsert
	private List<CaseTeamMember> QueueForUpsert = new List<CaseTeamMember>(); 
}